// Copyright (c) 2024 CHANGLEI. All rights reserved.

import 'package:charts_grasp/src/chart/axis/axis.dart';
import 'package:charts_grasp/src/chart/base/chart_base.dart';
import 'package:charts_grasp/src/chart/chart_segment/chart_segment.dart';
import 'package:charts_grasp/src/chart/chart_segment/column_segment.dart';
import 'package:charts_grasp/src/chart/chart_series/column_series.dart';
import 'package:charts_grasp/src/chart/chart_series/series.dart';
import 'package:charts_grasp/src/chart/chart_series/series_renderer_properties.dart';
import 'package:charts_grasp/src/chart/chart_series/xy_data_series.dart';
import 'package:charts_grasp/src/chart/common/cartesian_state_properties.dart';
import 'package:charts_grasp/src/chart/common/common.dart';
import 'package:charts_grasp/src/chart/common/renderer.dart';
import 'package:charts_grasp/src/chart/common/segment_properties.dart';
import 'package:charts_grasp/src/chart/user_interaction/zooming_panning.dart';
import 'package:charts_grasp/src/chart/utils/helper.dart';
import 'package:charts_grasp/src/common/event_args.dart';
import 'package:charts_grasp/src/common/rendering_details.dart';
import 'package:flutter/material.dart';

class ColumnSeriesRenderer extends XyDataSeriesRenderer {
  ColumnSeriesRenderer();

  late SeriesRendererDetails _currentSeriesDetails;
  late SeriesRendererDetails _segmentSeriesDetails;

  ChartSegment _createSegments(
      CartesianChartPoint<dynamic> currentPoint, int pointIndex, int seriesIndex, double animateFactor,) {
    _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this);
    final ColumnSegment segment = createSegment() as ColumnSegment;
    SegmentHelper.setSegmentProperties(segment, SegmentProperties(_currentSeriesDetails.stateProperties, segment));
    final SegmentProperties segmentProperties = SegmentHelper.getSegmentProperties(segment);
    final CartesianStateProperties stateProperties = _currentSeriesDetails.stateProperties;
    final List<CartesianSeriesRenderer> oldSeriesRenderers = _currentSeriesDetails.stateProperties.oldSeriesRenderers;
    final ColumnSeries<dynamic, dynamic> columnSeries = _currentSeriesDetails.series as ColumnSeries<dynamic, dynamic>;
    segmentProperties.seriesRenderer = this;
    segmentProperties.series = columnSeries;
    segmentProperties.seriesIndex = seriesIndex;
    segment.currentSegmentIndex = pointIndex;
    segment.points.add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y));
    segment.animationFactor = animateFactor;
    segmentProperties.currentPoint = currentPoint;
    final ZoomingBehaviorDetails zoomingBehaviorDetails =
        ZoomPanBehaviorHelper.getRenderingDetails(stateProperties.zoomPanBehaviorRenderer);
    _segmentSeriesDetails = SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer);
    if (stateProperties.renderingDetails.widgetNeedUpdate &&
        zoomingBehaviorDetails.isPinching != true &&
        !stateProperties.renderingDetails.isLegendToggled &&
        oldSeriesRenderers.isNotEmpty &&
        oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex &&
        SeriesHelper.getSeriesRendererDetails(oldSeriesRenderers[segmentProperties.seriesIndex]).seriesName ==
            _segmentSeriesDetails.seriesName) {
      segmentProperties.oldSeriesRenderer = oldSeriesRenderers[segmentProperties.seriesIndex];
      final SeriesRendererDetails segmentOldSeriesDetails =
          SeriesHelper.getSeriesRendererDetails(segmentProperties.oldSeriesRenderer!);
      segmentProperties.oldPoint = (segmentOldSeriesDetails.segments.isNotEmpty == true &&
              segmentOldSeriesDetails.segments[0] is ColumnSegment &&
              (segmentOldSeriesDetails.dataPoints.length - 1 >= pointIndex) == true)
          ? segmentOldSeriesDetails.dataPoints[pointIndex]
          : null;
      segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment);
      if ((stateProperties.selectedSegments.length - 1 >= pointIndex) &&
          SegmentHelper.getSegmentProperties(_currentSeriesDetails.stateProperties.selectedSegments[pointIndex])
                  .oldSegmentIndex ==
              null) {
        final ChartSegment selectedSegment = _currentSeriesDetails.stateProperties.selectedSegments[pointIndex];
        final SegmentProperties selectedSegmentProperties = SegmentHelper.getSegmentProperties(selectedSegment);
        selectedSegmentProperties.oldSeriesRenderer = oldSeriesRenderers[selectedSegmentProperties.seriesIndex];
        selectedSegmentProperties.seriesRenderer = this;
        selectedSegmentProperties.oldSegmentIndex = getOldSegmentIndex(selectedSegment);
      }
    } else if (stateProperties.renderingDetails.isLegendToggled &&
        stateProperties.segments.isNotEmpty) {
      segmentProperties.oldSeriesVisible = stateProperties.oldSeriesVisible[segmentProperties.seriesIndex];

      ColumnSegment oldSegment;
      for (int i = 0; i < stateProperties.segments.length; i++) {
        oldSegment = stateProperties.segments[i] as ColumnSegment;
        if (oldSegment.currentSegmentIndex == segment.currentSegmentIndex &&
            SegmentHelper.getSegmentProperties(oldSegment).seriesIndex == segmentProperties.seriesIndex) {
          segmentProperties.oldRegion = oldSegment.segmentRect.outerRect;
        }
      }
    }

    segmentProperties.path = findingRectSeriesDashedBorder(currentPoint, columnSeries.borderWidth);
    segment.segmentRect = getRRectFromRect(currentPoint.region!, columnSeries.borderRadius);

    if (columnSeries.isTrackVisible) {
      segmentProperties.trackRect = getRRectFromRect(currentPoint.trackerRectRegion!, columnSeries.borderRadius);
    }
    segmentProperties.segmentRect = segment.segmentRect;
    customizeSegment(segment);
    _currentSeriesDetails.segments.add(segment);
    return segment;
  }


  @override
  ChartSegment createSegment() => ColumnSegment();

  @override
  void customizeSegment(ChartSegment segment) {
    final ColumnSegment columnSegment = segment as ColumnSegment;
    final SegmentProperties segmentProperties = SegmentHelper.getSegmentProperties(columnSegment);
    segmentProperties.color = segmentProperties.currentPoint!.pointColorMapper ?? _segmentSeriesDetails.seriesColor;
    segmentProperties.strokeColor = segmentProperties.series.borderColor;
    segmentProperties.strokeWidth = segmentProperties.series.borderWidth;
    columnSegment.strokePaint = columnSegment.getStrokePaint();
    columnSegment.fillPaint = columnSegment.getFillPaint();
    segmentProperties.trackerFillPaint = segmentProperties.getTrackerFillPaint();
    segmentProperties.trackerStrokePaint = segmentProperties.getTrackerStrokePaint();
  }

  @override
  void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY,
      [CartesianSeriesRenderer? seriesRenderer,]) {
    final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer!);
    canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint);
    canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint);
  }

  @override
  void drawDataLabel(
          int index, Canvas canvas, String dataLabel, double pointX, double pointY, int angle, TextStyle style,) =>
      drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle);
}

class ColumnChartPainter extends CustomPainter {
  ColumnChartPainter(
      {required this.stateProperties,
      required this.seriesRenderer,
      required this.isRepaint,
      required this.animationController,
      required ValueNotifier<num> notifier,
      required this.painterKey,})
      : chart = stateProperties.chart,
        super(repaint: notifier);

  final CartesianStateProperties stateProperties;

  final CgCartesianChart chart;

  final bool isRepaint;

  final AnimationController animationController;

  List<ChartLocation> currentChartLocations = <ChartLocation>[];

  ColumnSeriesRenderer seriesRenderer;

  final PainterKey painterKey;

  @override
  void paint(Canvas canvas, Size size) {
    final SeriesRendererDetails seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer);
    disposeOldSegments(chart, seriesRendererDetails);
    final ChartAxisRendererDetails xAxisDetails = seriesRendererDetails.xAxisDetails!;
    final ChartAxisRendererDetails yAxisDetails = seriesRendererDetails.yAxisDetails!;
    final RenderingDetails renderingDetails = stateProperties.renderingDetails;
    final List<CartesianChartPoint<dynamic>> dataPoints = seriesRendererDetails.dataPoints;
    final ColumnSeries<dynamic, dynamic> series = seriesRendererDetails.series as ColumnSeries<dynamic, dynamic>;
    if (seriesRendererDetails.visible! == true) {
      Rect axisClipRect;
      Rect clipRect;
      double animationFactor;
      CartesianChartPoint<dynamic> point;
      canvas.save();
      assert(series.animationDuration >= 0,
          'The animation duration of the bar series must be greater than or equal to 0.',);
      final int seriesIndex = painterKey.index;
      seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex);
      axisClipRect = calculatePlotOffset(
          stateProperties.chartAxis.axisClipRect, Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset),);
      canvas.clipRect(axisClipRect);
      animationFactor =
          seriesRendererDetails.seriesAnimation != null ? seriesRendererDetails.seriesAnimation!.value : 1;
      stateProperties.shader = null;
      if (series.onCreateShader != null) {
        stateProperties.shader =
            series.onCreateShader!(ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series'));
      }

      int segmentIndex = -1;
      if (seriesRendererDetails.visibleDataPoints == null ||
          seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) {
        seriesRendererDetails.visibleDataPoints = <CartesianChartPoint<dynamic>>[];
      }

      seriesRendererDetails.setSeriesProperties(seriesRendererDetails);
      for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) {
        point = dataPoints[pointIndex];
        final bool withInXRange = withInRange(point.xValue, seriesRendererDetails.xAxisDetails!);
        final bool withInYRange =
            point.yValue != null && withInRange(point.yValue, seriesRendererDetails.yAxisDetails!);
        if (withInXRange || withInYRange) {
          seriesRendererDetails.calculateRegionData(
              stateProperties, seriesRendererDetails, painterKey.index, point, pointIndex,);
          if (point.isVisible && !point.isGap) {
            seriesRendererDetails.drawSegment(
                canvas, seriesRenderer._createSegments(point, segmentIndex += 1, painterKey.index, animationFactor),);
          }
        }
      }
      clipRect = calculatePlotOffset(
          Rect.fromLTRB(
              stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width,
              stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height,
              stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width,
              stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height,),
          Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset),);
      canvas.restore();
      if ((series.animationDuration <= 0 ||
              (!renderingDetails.initialRender! && seriesRendererDetails.needAnimateSeriesElements == false) ||
              animationFactor >= stateProperties.seriesDurationFactor) &&
          (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) {
        canvas.clipRect(clipRect);
        seriesRendererDetails.renderSeriesElements(chart, canvas, seriesRendererDetails.seriesElementAnimation);
      }
      if (animationFactor >= 1) {
        stateProperties.setPainterKey(painterKey.index, painterKey.name, true);
      }
    }
  }

  @override
  bool shouldRepaint(ColumnChartPainter oldDelegate) => isRepaint;
}
